package nes

import "rgones/nes/render"

func bgPalette(ppu *NesPPU, tileColumn int, tileRow int) [4]uint8 {
	attrTableIdx := tileRow/4*8 + tileColumn/4
	attrByte := ppu.Vram[0x3c0+attrTableIdx] // note: still using hardcoded first nametable

	var paletteIdx uint8
	switch {
	case tileColumn%4/2 == 0 && tileRow%4/2 == 0:
		paletteIdx = attrByte & 0b11
	case tileColumn%4/2 == 1 && tileRow%4/2 == 0:
		paletteIdx = (attrByte >> 2) & 0b11
	case tileColumn%4/2 == 0 && tileRow%4/2 == 1:
		paletteIdx = (attrByte >> 4) & 0b11
	case tileColumn%4/2 == 1 && tileRow%4/2 == 1:
		paletteIdx = (attrByte >> 6) & 0b11
	default:
		panic("should not happen")
	}

	paletteStart := 1 + int(paletteIdx)*4
	return [4]uint8{ppu.PaletteTable[0], ppu.PaletteTable[paletteStart], ppu.PaletteTable[paletteStart+1], ppu.PaletteTable[paletteStart+2]}
}

func spritePalette(ppu *NesPPU, paletteIdx uint8) [4]uint8 {
	start := 0x11 + int(paletteIdx)*4
	return [4]uint8{
		0,
		ppu.PaletteTable[start],
		ppu.PaletteTable[start+1],
		ppu.PaletteTable[start+2],
	}
}

func Render(ppu *NesPPU, frame *render.Frame) {
	bank := ppu.Ctrl.BkndPatternAddr()

	for i := 0; i < 0x3c0; i++ {
		tileAddr := uint16(ppu.Vram[i])
		tileColumn := i % 32
		tileRow := i / 32
		tile := ppu.ChrRom[bank+uint16(tileAddr)*16 : bank+uint16(tileAddr)*16+16]
		palette := bgPalette(ppu, tileColumn, tileRow)

		for y := 0; y <= 7; y++ {
			upper := tile[y]
			lower := tile[y+8]

			for x := 7; x >= 0; x-- {
				value := (1&lower)<<1 | (1 & upper)
				upper = upper >> 1
				lower = lower >> 1
				var rgb render.RGB
				switch value {
				case 0:
					rgb = render.SystemPalette[ppu.PaletteTable[0]]
				case 1:
					rgb = render.SystemPalette[palette[1]]
				case 2:
					rgb = render.SystemPalette[palette[2]]
				case 3:
					rgb = render.SystemPalette[palette[3]]
				default:
					panic("can't be")
				}
				frame.SetPixel(tileColumn*8+x, tileRow*8+y, rgb.R, rgb.G, rgb.B)
			}
		}
	}

	for i := len(ppu.OamData) - 1; i >= 0; i -= 4 {
		tileIdx := uint16(ppu.OamData[i+1])
		tileX := int(ppu.OamData[i+3])
		tileY := int(ppu.OamData[i])

		flipVertical := ppu.OamData[i+2]>>7&1 == 1
		flipHorizontal := ppu.OamData[i+2]>>6&1 == 1
		paletteIdx := ppu.OamData[i+2] & 0b11
		spritePalette := spritePalette(ppu, paletteIdx)
		bank := int(ppu.Ctrl.SprtPatternAddr())

		tile := ppu.ChrRom[bank+int(tileIdx)*16 : bank+int(tileIdx)*16+16]

		for y := 0; y <= 7; y++ {
			upper := tile[y]
			lower := tile[y+8]

			for x := 7; x >= 0; x-- {
				value := (1&lower)<<1 | (1 & upper)
				upper = upper >> 1
				lower = lower >> 1
				var rgb render.RGB
				switch value {
				case 0:
					continue // skip coloring the pixel
				case 1:
					rgb = render.SystemPalette[spritePalette[1]]
				case 2:
					rgb = render.SystemPalette[spritePalette[2]]
				case 3:
					rgb = render.SystemPalette[spritePalette[3]]
				default:
					panic("can't be")
				}
				switch {
				case !flipHorizontal && !flipVertical:
					frame.SetPixel(tileX+x, tileY+y, rgb.R, rgb.G, rgb.B)
				case flipHorizontal && !flipVertical:
					frame.SetPixel(tileX+7-x, tileY+y, rgb.R, rgb.G, rgb.B)
				case !flipHorizontal && flipVertical:
					frame.SetPixel(tileX+x, tileY+7-y, rgb.R, rgb.G, rgb.B)
				case flipHorizontal && flipVertical:
					frame.SetPixel(tileX+7-x, tileY+7-y, rgb.R, rgb.G, rgb.B)
				}
			}
		}
	}
}
